﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.IO;

namespace TiebaSpider.Core
{
    public class DownloadEngine
    {
        private string downloadDirectory = string.Empty;
        private Crawler crawler;
        private bool captureSign;
        private List<string> batchTaskImageIDs;
        private List<string> batchOriginalImageURL;

        public DownloadCompleteEventArgs DCEventArgs { get; private set; }
        public BatchCompleteEventArgs BCEventArgs { get; private set; }
        public Beans.DownloadTask Task { get; set; }

        public event EventHandler StartTrack;
        public event EventHandler StartDownload;
        public event EventHandler<TrackEventArgs> TrackComplete;
        public event EventHandler<DownloadEventArgs> DownloadComplete;
        public event EventHandler<BatchCompleteEventArgs> BatchComplete;
        public event EventHandler<DownloadCompleteEventArgs> TaskComplete;

        public DownloadEngine()
        {
            crawler = new Crawler();
            crawler.CrawlerEvent += OnCrawlerComplete;
            TrackComplete += OnTrackComplete;
            DownloadComplete += OnDownloadComplete;
            BatchComplete += OnBatchComplete;
            DCEventArgs = new DownloadCompleteEventArgs();
            BCEventArgs = new BatchCompleteEventArgs();
            batchTaskImageIDs = new List<string>();
            batchOriginalImageURL = new List<string>();
            captureSign = false;
        }

        public DownloadEngine(Beans.DownloadTask task)
            : this()
        {
            Task = task;
        }

        private void OnCrawlerComplete(object sender, CrawlerEventArgs e)
        {
            captureSign = true;
        }

        private void OnTrackComplete(object sender, TrackEventArgs e)
        {
            if (e.Success)
            {
                DCEventArgs.SuccessTracking++;
                BCEventArgs.SuccessTracking++;
                batchOriginalImageURL.Add(e.ResultURL);
            }
            else
            {
                DCEventArgs.FailureTracking++;
                BCEventArgs.FailureTracking++;
            }
            if ((BCEventArgs.SuccessTracking + BCEventArgs.FailureTracking) == batchTaskImageIDs.Count)
            {
                startDownload();
            }
        }

        private void OnDownloadComplete(object sender, DownloadEventArgs e)
        {
            if (e != null)
            {
                if (e.Success)
                {
                    DCEventArgs.SuccessDownload++;
                    BCEventArgs.SuccessDownload++;
                }
                else
                {
                    DCEventArgs.FailureDownload++;
                    BCEventArgs.FailureDownload++;
                }
            }
            if ((BCEventArgs.SuccessDownload + BCEventArgs.FailureDownload + BCEventArgs.FailureTracking) == batchTaskImageIDs.Count)
            {
                BCEventArgs.CompletedBatch = Task.DeployedBatch;
                if (BatchComplete != null)
                    BatchComplete(this, BCEventArgs);
            }
        }

        private void OnBatchComplete(object sender, BatchCompleteEventArgs e)
        {
            if ((DCEventArgs.SuccessDownload + DCEventArgs.FailureDownload + DCEventArgs.FailureTracking) == Task.SelectedImageID.Count)
            {
                if (TaskComplete != null)
                    TaskComplete(this, DCEventArgs);
            }
            else
            {
                Task.DeployedBatch = e.CompletedBatch + 1;
                BCEventArgs.Reset();
                startBatch();
            }
        }

        public static bool CheckDownloaded(string threadName, string targetDir, string forumName, bool inSub)
        {
            char[] invalidChars = Path.GetInvalidFileNameChars();
            string downloadDirectory = string.Empty;
            foreach (char invalid in invalidChars)
            {
                threadName = threadName.Replace(invalid, ' ');
            }
            if (inSub)
            {
                downloadDirectory = Path.Combine(targetDir, forumName, threadName);
            }
            else
            {
                downloadDirectory = Path.Combine(targetDir, threadName);
            }
            return Directory.Exists(downloadDirectory);
        }

        public void StartTask()
        {
            char[] invalidChars = Path.GetInvalidFileNameChars();
            foreach (char invalid in invalidChars)
            {
                Task.ThreadName = Task.ThreadName.Replace(invalid, ' ');
            }
            if (Task.CreateSubDir)
            {
                downloadDirectory = Task.TargetDir + Path.DirectorySeparatorChar + Task.ForumName + Path.DirectorySeparatorChar + Task.ThreadName;
            }
            else
            {
                downloadDirectory = Task.TargetDir + Path.DirectorySeparatorChar + Task.ForumName;
            }
            if (!Directory.Exists(downloadDirectory))
                Directory.CreateDirectory(downloadDirectory);
            if (Task.DeployedBatch == 0) Task.DeployedBatch = 1;
            startBatch();
        }

        private void startBatch()
        {
            batchTaskImageIDs = (from s in Task.SelectedImageID
                                 select s).Skip((Task.DeployedBatch - 1) * Task.BatchSize).Take(Task.BatchSize).ToList();
            BCEventArgs.BatchSize = batchTaskImageIDs.Count;
            startTrack();
        }

        private void startTrack()
        {
            if (StartTrack != null)
                StartTrack(this, new EventArgs());
            batchOriginalImageURL.Clear();
            List<string> batchClone = new List<string>(batchTaskImageIDs);
            foreach (string iId in batchClone)
            {
                trackDownloadURL(iId);
            }
        }

        private void startDownload()
        {
            if (StartDownload != null)
                StartDownload(this, new EventArgs());
            List<string> batchClone = new List<string>(batchOriginalImageURL);
            if (batchClone.Count > 0)
            {
                foreach (string item in batchClone)
                {
                    downloadImage(item);
                }
            }
            else
            {
                if (DownloadComplete != null)
                    DownloadComplete(this, null);
            }
        }

        private async void trackDownloadURL(string imageID)
        {
            TrackEventArgs tea = new TrackEventArgs();
            crawler.TargetURL = string.Format(Beans.AnalysisExpressions.PictureLargeViewUrl, Task.ForumEncodedName, Task.ThreadID, imageID);
            crawler.TimeOutThreshold = 30000;
            captureSign = false;
            try
            {
                string largeViewContent = await crawler.GetStringContent();
                while (!captureSign) { }
                if (!largeViewContent.Equals(string.Empty))
                {
                    Match deleteMatch = Regex.Match(largeViewContent, Beans.AnalysisExpressions.PictureLargeViewCaptureFailureSign, RegexOptions.IgnoreCase);
                    if (deleteMatch.Success) throw new TaskCanceledException();
                    Match originURLMatch = Regex.Match(largeViewContent, Beans.AnalysisExpressions.PictureCaptureFromLargeView, RegexOptions.IgnoreCase);
                    if (originURLMatch.Success)
                    {
						try
						{
							tea.ResultURL = originURLMatch.Groups[1].Value.Replace("\\", "");
						}
						catch (OutOfMemoryException)
						{
							GC.Collect();
							tea.Success = false;
						}
						tea.Success = true;
                    }
                    else
                    {
                        tea.Success = false;
                    }
                }
            }
            catch (Core.RetreivingCanceledException rce)
            {
                tea.Success = false;
            }
            catch (NullReferenceException nre)
            {
                tea.Success = false;
            }
            catch (TaskCanceledException tce)
            {
                tea.Success = false;
            }
            if (TrackComplete != null)
                TrackComplete(this, tea);
        }

        private async void downloadImage(string originUrl)
        {
            Directory.SetCurrentDirectory(downloadDirectory);
            DownloadEventArgs dea = new DownloadEventArgs();
            crawler.TargetURL = originUrl;
            crawler.TimeOutThreshold = 60000;
            string[] imageURLUnit = originUrl.Split('/');
            string imageName = imageURLUnit[imageURLUnit.Length - 1];
            captureSign = false;
            try
            {
                if (File.Exists(imageName))
                {
                    throw new TaskLeapedException("File exists");
                }
                byte[] imageContent = await crawler.GetByteArrayContent();
                while (!captureSign) { }
                if (imageContent.Length != 0)
                {
                    File.WriteAllBytes(imageName, imageContent);
                    dea.Success = true;
                }
                else
                {
                    dea.Success = false;
                }
            }
            catch (TaskLeapedException tle)
            {
                dea.Success = true;
            }
            catch (Core.RetreivingCanceledException rce)
            {
                dea.Success = false;
            }
            catch (NullReferenceException nre)
            {
                dea.Success = false;
            }
            catch (TaskCanceledException tce)
            {
                dea.Success = false;
            }
            if (DownloadComplete != null)
                DownloadComplete(this, dea);
        }
    }

    public class TrackEventArgs : EventArgs
    {
        public bool Success { get; set; }
        public string ResultURL { get; set; }

        public TrackEventArgs() { }

        public TrackEventArgs(bool success, string url)
        {
            Success = success;
            ResultURL = url;
        }
    }

    public class DownloadEventArgs : EventArgs
    {
        public bool Success { get; set; }

        public DownloadEventArgs() { }

        public DownloadEventArgs(bool success, string id)
        {
            Success = success;
        }
    }

    public class DownloadCompleteEventArgs : EventArgs
    {
        public int SuccessTracking { get; set; }
        public int SuccessDownload { get; set; }
        public int FailureTracking { get; set; }
        public int FailureDownload { get; set; }

        public DownloadCompleteEventArgs()
        {
            SuccessTracking = 0;
            SuccessDownload = 0;
            FailureTracking = 0;
            FailureDownload = 0;
        }
    }

    public class BatchCompleteEventArgs : EventArgs
    {
        public int CompletedBatch { get; set; }
        public int BatchSize { get; set; }
        public int SuccessTracking { get; set; }
        public int SuccessDownload { get; set; }
        public int FailureTracking { get; set; }
        public int FailureDownload { get; set; }

        public BatchCompleteEventArgs()
        {
            CompletedBatch = 0;
            BatchSize = 0;
            SuccessTracking = 0;
            SuccessDownload = 0;
            FailureTracking = 0;
            FailureDownload = 0;
        }

        public void Reset()
        {
            CompletedBatch = 0;
            BatchSize = 0;
            SuccessTracking = 0;
            SuccessDownload = 0;
            FailureTracking = 0;
            FailureDownload = 0;
        }
    }

    [Serializable]
    public class TaskLeapedException : ApplicationException
    {
        public TaskLeapedException() { }
        public TaskLeapedException(string message) : base(message) { }
        public TaskLeapedException(string message, Exception inner) : base(message, inner) { }
    }
}
